#include "Task.hpp"
#include <unistd.h>
#include <string>
#include <vector>
#include <cstdlib>
#include <ctime>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;

const int processnum = 5; // 要创建的进程数量
vector<task_t> tasks;     // 存放任务

// 先描述
class channel
{
public:
    channel(int cmdfd, pid_t slaveid, string &processname)
        : _cmdfd(cmdfd), _slaveid(slaveid), _processname(processname)
    {
    }

public:
    int _cmdfd;          // 发送任务的文件描述符
    pid_t _slaveid;      // 子进程的id
    string _processname; // 子进程的名字--方便我们打印日志
};

void Reader()
{
    // 子进程一直阻塞，等待父进程派发任务
    while (true)
    {
        int task = 0;
        int n = read(0, &task, sizeof(task));
        if (n != 0)
        {
            std::cout << "slaver say@ get a command: " << getpid() << " : cmdid: " << task << std::endl;
            tasks[task]();
        }
        else
            break;

        sleep(1);
    }
};

//solution2：定义一个数组 存储写端，在子进程中进行关闭。
void InitProcessPool(vector<channel> *channels)
{
    vector<int> wfds;//用来存储父进程的写端
    for (int i = 0; i < processnum; i++)
    {
        int pipefd[2] = {0};
        int n = pipe(pipefd);
        wfds.push_back(pipefd[1]);
        if (n < 0)
        {
            perror("pipe");
            exit(1);
        }

        pid_t id = fork();
        if (id == 0)
        {
            // child -> read
            close(pipefd[1]);   // 关闭写端口
            for(auto e:wfds) close(e);  //关闭继承父进程的写端
            dup2(pipefd[0], 0); // 把从管道读的端口重定向到从键盘读的端口 方便我们子进程接收任务
            // 接收任务
            Reader();

            exit(0);
        }
        else if (id < 0)
        {
            perror("fork");
            exit(2);
        }
        else
        {
            // father -> write
            string processname = "process - " + to_string(i);
            channels->push_back(channel(pipefd[1], id, processname));

            close(pipefd[0]); // 关闭读端口
        }
    }
}

void CtrlSlaver(const vector<channel> &channels)
{
    int num = 0;                 // 控制子进程轮转
    for (int i = 0; i < 10; i++) // 指定任务数量
    {
        // 2.1选择任务
        int cmdid = rand() % tasks.size();

        // 2.2选择子进程
        // int processid = rand() % channels.size(); // 随机选择
        int processid = num; // 轮转

        // 2.3发送任务
        cout << "father say: "
             << " cmdid: " << cmdid << " already sendto " << channels[processid]._slaveid << " process name: "
             << channels[processid]._processname << endl;

        write(channels[processid]._cmdfd, &cmdid, sizeof(cmdid)); // 通过write写入到管道来给子进程发送任务

        num++;
        num %= channels.size();

        sleep(1);
    }
}

// 当写端关闭 子进程会读到0 做判断 return子进程 父进程回收子进程资源
void QuitProcess(const vector<channel> &channels)
{
    //子进程继承了前面的写端 这样释放不掉
    for (auto &e : channels)
    {
        close(e._cmdfd);
        waitpid(e._slaveid, nullptr, 0);
    } 

    sleep(5);
    //solution1： 当我们关闭玩所有的写端 ，最后一个子进程没人指向 ，所以最后一个子进程会被释放，此时倒数第二个子进程也会退，依次类推。
//    for(auto e:channels) close(e._cmdfd);
//    for(auto e:channels) waitpid(e._slaveid,nullptr,0);

}

int main()
{
    srand(time(nullptr)); // 种随机数种子

    LoadTask(&tasks);
    // 再组织
    vector<channel> channels;

    // 1.初始化
    InitProcessPool(&channels);

    // 2.控制子进程
    CtrlSlaver(channels);

    // 3.清理收尾
    QuitProcess(channels);

    return 0;
}